package com.lishuaihua.toast

import android.R
import android.app.AppOpsManager
import android.app.Application
import android.app.NotificationManager
import android.content.Context
import android.content.res.Resources.NotFoundException
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import com.lishuaihua.toast.interceptor.IToastInterceptor
import com.lishuaihua.toast.interceptor.ToastInterceptor
import com.lishuaihua.toast.strategy.IToastStrategy
import com.lishuaihua.toast.strategy.ToastStrategy
import com.lishuaihua.toast.style.IToastStyle
import com.lishuaihua.toast.style.ToastBlackStyle
import com.lishuaihua.toast.toast.BaseToast
import com.lishuaihua.toast.toast.SafeToast
import com.lishuaihua.toast.toast.SupportToast
import java.lang.reflect.InvocationTargetException

object ToastUtils {
    private var sInterceptor: IToastInterceptor? = null
    private var sStrategy: IToastStrategy? = null

    /**
     * 获取当前Toast对象
     */
    var toast: Toast? = null
        private set

    /**
     * 初始化 ToastUtils，在 Application 中初始化
     *
     * @param application       应用的上下文
     */
    @JvmOverloads
    fun init(
        application: Application,
        style: IToastStyle = ToastBlackStyle(application)
    ) {
        checkNullPointer(application)
        // 初始化 Toast 拦截器
        if (sInterceptor == null) {
            setToastInterceptor(ToastInterceptor())
        }

        // 初始化 Toast 显示处理器
        if (sStrategy == null) {
            setToastStrategy(ToastStrategy())
        }

        // 初始化吐司
        if (areNotificationsEnabled(application)) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + 4) {
                // 适配 Android 11 无法使用自定义 Toast 的问题
                // 官方文档：https://developer.android.google.cn/preview/features/toasts
                setToast(SupportToast(application))
            } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
                // 解决 Android 7.1 上主线程被阻塞后吐司会报错的问题
                setToast(SafeToast(application))
            } else {
                setToast(BaseToast(application))
            }
        } else {
            // 解决关闭通知栏权限后 Toast 不显示的问题
            setToast(SupportToast(application))
        }

        // 设置 Toast 视图
        setView(createTextView(application, style))

        // 设置 Toast 重心
        setGravity(style.gravity, style.xOffset, style.yOffset)
    }



    /**
     * 显示一个吐司
     *
     * @param id      如果传入的是正确的 string id 就显示对应字符串
     * 如果不是则显示一个整数的string
     */
    fun show(id: Int) {
        checkToastState()
        try {
            // 如果这是一个资源 id
            show(context.resources.getText(id))
        } catch (ignored: NotFoundException) {
            // 如果这是一个 int 整数
            show(id.toString())
        }
    }

    /**
     * 显示一个吐司
     *
     * @param text      需要显示的文本
     */
    @Synchronized
    fun show(text: CharSequence?) {
        checkToastState()
        if (sInterceptor!!.intercept(toast, text)) {
            return
        }
        sStrategy!!.show(text)
    }

    /**
     * 取消吐司的显示
     */
    @Synchronized
    fun cancel() {
        checkToastState()
        sStrategy!!.cancel()
    }

    /**
     * 设置吐司的位置
     *
     * @param gravity           重心
     * @param xOffset           x轴偏移
     * @param yOffset           y轴偏移
     */
    fun setGravity(gravity: Int, xOffset: Int, yOffset: Int) {
        var gravity = gravity
        checkToastState()

        // 适配 Android 4.2 新特性，布局反方向（开发者选项 - 强制使用从右到左的布局方向）
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            gravity = Gravity.getAbsoluteGravity(
                gravity,
                toast!!.view.resources.configuration.layoutDirection
            )
        }
        toast!!.setGravity(gravity, xOffset, yOffset)
    }

    /**
     * 给当前Toast设置新的布局，具体实现可看[BaseToast.setView]
     */
    fun setView(id: Int) {
        checkToastState()
        setView(View.inflate(context, id, null))
    }

    fun setView(view: View) {
        checkToastState()

        // 这个 View 不能为空
        checkNullPointer(view)

        // 当前必须用 Application 的上下文创建的 View，否则可能会导致内存泄露
        val context = view.context
        require(context is Application) { "The view must be initialized using the context of the application" }

        // 如果吐司已经创建，就重新初始化吐司
        if (toast != null) {
            // 取消原有吐司的显示
            toast!!.cancel()
            toast!!.view = view
        }
    }

    /**
     * 获取当前 Toast 的视图
     */
    fun <V : View?> getView(): V {
        checkToastState()
        return toast!!.view as V
    }

    /**
     * 初始化全局的Toast样式
     *
     * @param style         样式实现类，框架已经实现三种不同的样式
     * 黑色样式：[ToastBlackStyle]
     * 白色样式：[ToastWhiteStyle]
     * 仿QQ样式：[ToastQQStyle]
     */
    fun initStyle(style: IToastStyle) {
        checkNullPointer(style)
        // 如果吐司已经创建，就重新初始化吐司
        if (toast != null) {
            // 取消原有吐司的显示
            toast!!.cancel()
            toast!!.view = createTextView(context, style)
            toast!!.setGravity(style.gravity, style.xOffset, style.yOffset)
        }
    }

    /**
     * 设置当前Toast对象
     */
    fun setToast(toast: Toast) {
        checkNullPointer(toast)
        if (this.toast != null && toast.view == null) {
            // 移花接木
            toast.view = toast.view
            toast.setGravity(
                toast.gravity,
                toast.xOffset,
                toast.yOffset
            )
            toast.setMargin(
                toast.horizontalMargin,
                toast.verticalMargin
            )
        }
        this.toast = toast
        if (sStrategy != null) {
            sStrategy!!.bind(this.toast)
        }
    }

    /**
     * 设置 Toast 显示策略
     */
    fun setToastStrategy(handler: IToastStrategy?) {
        checkNullPointer(handler)
        sStrategy = handler
        if (toast != null) {
            sStrategy!!.bind(toast)
        }
    }

    /**
     * 设置 Toast 拦截器（可以根据显示的内容决定是否拦截这个Toast）
     * 场景：打印 Toast 内容日志、根据 Toast 内容是否包含敏感字来动态切换其他方式显示（这里可以使用我的另外一套框架 XToast）
     */
    fun setToastInterceptor(interceptor: IToastInterceptor?) {
        checkNullPointer(interceptor)
        sInterceptor = interceptor
    }

    /**
     * 检查吐司状态，如果未初始化请先调用[ToastUtils.init]
     */
    private fun checkToastState() {
        // 吐司工具类还没有被初始化，必须要先调用init方法进行初始化
        checkNotNull(toast) { "ToastUtils has not been initialized" }
    }

    /**
     * 检查对象是否为空
     */
    private fun checkNullPointer(`object`: Any?) {
        if (`object` == null) {
            throw NullPointerException("are you ok?")
        }
    }

    /**
     * 生成默认的 TextView 对象
     */
    private fun createTextView(context: Context, style: IToastStyle): TextView {
        val drawable = GradientDrawable()
        // 设置背景色
        drawable.setColor(style.backgroundColor)
        // 设置圆角大小
        drawable.cornerRadius = style.cornerRadius.toFloat()
        val textView = TextView(context)
        textView.id = R.id.message
        textView.setTextColor(style.textColor)
        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, style.textSize)

        // 适配布局反方向
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            textView.setPaddingRelative(
                style.paddingStart,
                style.paddingTop,
                style.paddingEnd,
                style.paddingBottom
            )
        } else {
            textView.setPadding(
                style.paddingStart,
                style.paddingTop,
                style.paddingEnd,
                style.paddingBottom
            )
        }
        textView.layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )

        // setBackground API 版本兼容
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            textView.background = drawable
        } else {
            textView.setBackgroundDrawable(drawable)
        }

        // 设置 Z 轴阴影
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            textView.z = style.z.toFloat()
        }

        // 设置最大显示行数
        if (style.maxLines > 0) {
            textView.maxLines = style.maxLines
        }
        return textView
    }

    /**
     * 获取上下文对象
     */
    private val context: Context
        private get() {
            checkToastState()
            return toast!!.view.context
        }

    /**
     * 检查通知栏权限有没有开启
     * 参考 SupportCompat 包中的方法： NotificationManagerCompat.from(context).areNotificationsEnabled();
     */
    private fun areNotificationsEnabled(context: Context): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val manager =
                context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            manager != null && manager.areNotificationsEnabled()
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            val appOps =
                context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
            val appInfo = context.applicationInfo
            val packageName = context.applicationContext.packageName
            val uid = appInfo.uid
            try {
                val appOpsClass =
                    Class.forName(AppOpsManager::class.java.name)
                val checkOpNoThrowMethod = appOpsClass.getMethod(
                    "checkOpNoThrow",
                    Integer.TYPE,
                    Integer.TYPE,
                    String::class.java
                )
                val opPostNotificationValue =
                    appOpsClass.getDeclaredField("OP_POST_NOTIFICATION")
                val value = opPostNotificationValue[Int::class.java] as Int
                checkOpNoThrowMethod.invoke(
                    appOps,
                    value,
                    uid,
                    packageName
                ) as Int == AppOpsManager.MODE_ALLOWED
            } catch (ignored: ClassNotFoundException) {
                true
            } catch (ignored: NoSuchMethodException) {
                true
            } catch (ignored: NoSuchFieldException) {
                true
            } catch (ignored: InvocationTargetException) {
                true
            } catch (ignored: IllegalAccessException) {
                true
            } catch (ignored: RuntimeException) {
                true
            }
        } else {
            true
        }
    }
}